package pl.elka.simulator.persistance.xml;

import pl.elka.simulator.core.AreaConstraint;
import pl.elka.simulator.core.Constraint;
import pl.elka.simulator.core.EnviromentConfiguration;
import pl.elka.simulator.core.Node;
import pl.elka.simulator.core.Position;
import pl.elka.simulator.core.Simulator;
import pl.elka.simulator.core.Treasure;
import pl.elka.simulator.core.configurationCommand.ables.IAbleToAreaConstraintConfiguration;
import pl.elka.simulator.core.configurationCommand.ables.IAbleToConstraintConfiguration;
import pl.elka.simulator.core.configurationCommand.ables.IAbleToNodeConfiguration;
import pl.elka.simulator.core.configurationCommand.ables.IAbleToTreasureConfiguration;
import pl.elka.simulator.core.mobilityManager.IMobilityManager;
import pl.elka.simulator.core.mobilityManager.MobilityManagerFactory;
import pl.elka.simulator.core.mobilityManager.MobilityManagerType;
import pl.elka.simulator.persistance.IPersister;
import pl.elka.simulator.persistance.PersistenceType;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
//DOM
import org.w3c.dom.*;
//Xerces classes.
import org.apache.xerces.dom.DocumentImpl;
import org.apache.xerces.parsers.DOMParser;
import org.apache.xml.serialize.*;

public class XMLPersister implements IPersister{

	@Override
	public PersistenceType getPersistanceType() {
		return PersistenceType.XML;
	}

	@SuppressWarnings({ "deprecation"})
	@Override
	public void persist(Simulator simulator, String destiny) {		
		try{			
			
			Document xmldoc= new DocumentImpl();			
			Element root = xmldoc.createElement("Enviroment");			
			Element enviromentConfigsElement = xmldoc.createElement("EnviromentConfig");
			root.appendChild(enviromentConfigsElement);
			EnviromentConfiguration ec = simulator.getEnviromentConfiguration();
			persistEnviroment(xmldoc,enviromentConfigsElement,ec);
			
			Element managersElement = xmldoc.createElement("MobilityManagers");
			root.appendChild(managersElement);
			
			List<IMobilityManager> mms = simulator.getMobilityManagersFull();
			for(IMobilityManager mm:mms){
				persistMobilityManager(xmldoc, managersElement, mm);
			}
			xmldoc.appendChild(root);
			
			FileOutputStream fos = new FileOutputStream(destiny);
			// XERCES 1 or 2 additionnal classes.
			OutputFormat of = new OutputFormat("XML","ISO-8859-1",true);
			of.setIndent(1);
			of.setIndenting(true);			
			XMLSerializer serializer = new XMLSerializer(fos,of);
			// As a DOM Serializer
			serializer.asDOMSerializer();
			serializer.serialize( xmldoc.getDocumentElement() );
			fos.close();
		}catch(Exception e){
			throw new RuntimeException(e);
		}
	}

	private void persistNodes(Document xmldoc, Element mmElement,IAbleToNodeConfiguration atnc) {
		List<pl.elka.simulator.core.Node> nodes = atnc.getNodes();
		if(nodes!=null && !nodes.isEmpty()){
			Element nodesElement = xmldoc.createElement("Nodes");
			mmElement.appendChild(nodesElement);
			for(Node n : nodes){
				Position pos = n.getPosition();
				Element nodeElement = xmldoc.createElement("Node");
				nodeElement.setAttribute("x", Float.toString(pos.getX()));
				nodeElement.setAttribute("y", Float.toString(pos.getY()));
				nodeElement.setAttribute("r", Float.toString(n.getRadius()));
				nodesElement.appendChild(nodeElement);
			}
		}
	}
	
	private void persistEnviroment(Document xmldoc, Element enviromentConfigsElement, EnviromentConfiguration ec) {		
		persistNodes(xmldoc, enviromentConfigsElement, ec);
		persistTreasure(xmldoc, enviromentConfigsElement, ec);
		persistConstraints(xmldoc, enviromentConfigsElement, ec);
		persistAreaConstraints(xmldoc, enviromentConfigsElement, ec);
	}

	private void persistMobilityManager(Document xmldoc,Element managersElement, IMobilityManager mm) {
		Element mmElement = xmldoc.createElement("Manager");
		
		mmElement.setAttribute("id", mm.getId());
		mmElement.setAttribute("type", mm.getType().getName());
		
		persistNodesIfAvailable(xmldoc, mm, mmElement);
		persistConstraintsIfAvailable(xmldoc, mm, mmElement);
		
		managersElement.appendChild(mmElement);
	}

	private void persistConstraintsIfAvailable(Document xmldoc,
			IMobilityManager mm, Element mmElement) {
		if(mm instanceof IAbleToConstraintConfiguration){
			IAbleToConstraintConfiguration atcc = (IAbleToConstraintConfiguration)mm;
			persistConstraints(xmldoc, mmElement, atcc);
		}
	}
	private void persistAreaConstraints(Document xmldoc, Element mmElement,IAbleToAreaConstraintConfiguration atcc) {
		AreaConstraint ac = atcc.getAreaConstraint();
		if(ac!=null){			
				Element constraintElement = xmldoc.createElement("AreaConstraint");
				mmElement.appendChild(constraintElement);				
				List<Position> positions = ac.getEdges();					
				for(Position p:positions){						
					Element vertexElement = xmldoc.createElement("Vertex");
					constraintElement.appendChild(vertexElement);						
					vertexElement.setAttribute("x", Float.toString(p.getX()));
					vertexElement.setAttribute("y", Float.toString(p.getY()));						
				}
		}
	}
	private void persistConstraints(Document xmldoc, Element mmElement,
			IAbleToConstraintConfiguration atcc) {
		List<Constraint> constraints = atcc.getConstraints();
		if(constraints!=null && !constraints.isEmpty()){
			Element constraintsElement = xmldoc.createElement("Constraints");
			mmElement.appendChild(constraintsElement);
			for(Constraint c:constraints){
				Element constraintElement = xmldoc.createElement("Constraint");
				constraintsElement.appendChild(constraintElement);
				
				List<Position> positions = c.getEdges();					
				for(Position p:positions){						
					Element vertexElement = xmldoc.createElement("Vertex");
					constraintElement.appendChild(vertexElement);						
					vertexElement.setAttribute("x", Float.toString(p.getX()));
					vertexElement.setAttribute("y", Float.toString(p.getY()));						
				}
			}
		}
	}

	private void persistNodesIfAvailable(Document xmldoc, IMobilityManager mm,
			Element mmElement) {
		if(mm instanceof IAbleToNodeConfiguration){
			IAbleToNodeConfiguration atnc = (IAbleToNodeConfiguration) mm;
			persistNodes(xmldoc, mmElement, atnc);
		}
	}
	
	private void persistTreasure(Document xmldoc, Element mmElement,IAbleToTreasureConfiguration attc) {
		List<Treasure> treasures = attc.getTreasures();
		if(treasures!=null && !treasures.isEmpty()){
			Element nodesElement = xmldoc.createElement("Treasures");
			mmElement.appendChild(nodesElement);
			for(Treasure n : treasures){
				Position pos = n.getPosition();
				Element nodeElement = xmldoc.createElement("Treasure");
				nodeElement.setAttribute("x", Float.toString(pos.getX()));
				nodeElement.setAttribute("y", Float.toString(pos.getY()));
				nodeElement.setAttribute("r", Float.toString(n.getRadius()));
				nodesElement.appendChild(nodeElement);
			}
		}
	}

	public Simulator load(String source){
		try{
			Simulator simulator = new Simulator(); 
			DOMParser parser = new DOMParser();
			parser.parse(new File(source).getAbsolutePath());
			Document parsedXML = parser.getDocument();
			Element mainElement = parsedXML.getDocumentElement();
	        
			Element enviromentConfigElement = ((Element)mainElement.getElementsByTagName("EnviromentConfig").item(0));
			loadEnviroment(simulator,enviromentConfigElement);
					
			
			NodeList managersNodes = ((Element)mainElement.getElementsByTagName("MobilityManagers").item(0)).getElementsByTagName("Manager");
			
			for(int i=0;i<managersNodes.getLength();++i){
				Element managerElement = (Element) managersNodes.item(i);
                IMobilityManager mm = loadManagers(managersNodes, managerElement);                
                simulator.addMobilityManager(mm);               
            }
			
			return simulator;
		}catch (Exception e) {
			throw new RuntimeException(e);
		}		
	}

	private void loadEnviroment(Simulator simulator,Element enviromentConfigElement) {
		EnviromentConfiguration ec = simulator.getEnviromentConfiguration();
		loadNodes(enviromentConfigElement, ec);
		loadConstraints(enviromentConfigElement, ec);
		loadAreaConstraints(enviromentConfigElement, ec);
		loadTreasures(enviromentConfigElement,ec);		
	}

	private IMobilityManager loadManagers(NodeList managersNodes, Element managerElement ) {
		String id = managerElement.getAttribute("id");
		String typeName = managerElement.getAttribute("type");
		
		MobilityManagerType type = MobilityManagerType.getByName(typeName);
		IMobilityManager mm = MobilityManagerFactory.getInstance().getMobilityManagerByType(type);
		mm.setId(id);
		
		loadNodesIfAvailable(managerElement, mm);		
		loadConstraintsIfAvailable(managerElement, mm);
		
		return mm;
	}

	private void loadConstraintsIfAvailable(Element managerElement,
			IMobilityManager mm) {
		if(mm instanceof IAbleToConstraintConfiguration){
			IAbleToConstraintConfiguration atcc = (IAbleToConstraintConfiguration)mm;			
			loadConstraints(managerElement, atcc);
		}
	}

	private void loadAreaConstraints(Element managerElement,IAbleToAreaConstraintConfiguration atcc) {
		AreaConstraint constraints = null;
		
		NodeList constraintsNodes = managerElement.getElementsByTagName("AreaConstraint");
		
		for(int i = 0; i <constraintsNodes.getLength(); ++i){
			Element constraintElement = (Element)constraintsNodes.item(i);
			NodeList vertexesNodes = constraintElement.getElementsByTagName("Vertex");
			List<Position> vertexes =  new ArrayList<Position>();
			
			for(int j = 0; j<vertexesNodes.getLength();++j){
				Element vertexElement = (Element)vertexesNodes.item(j);
				float x = Float.parseFloat(vertexElement.getAttribute("x"));
				float y = Float.parseFloat(vertexElement.getAttribute("y"));
				
				vertexes.add(new Position(x,y));
			}				
			constraints = new AreaConstraint(vertexes);
		}
		
		
		atcc.setAreaConstraint(constraints);
	}
	
	private void loadConstraints(Element managerElement,
			IAbleToConstraintConfiguration atcc) {
		List<Constraint> constraints =  new ArrayList<Constraint>();
		
		NodeList constraintsNodes = managerElement.getElementsByTagName("Constraint");
		
		for(int i = 0; i <constraintsNodes.getLength(); ++i){
			Element constraintElement = (Element)constraintsNodes.item(i);
			NodeList vertexesNodes = constraintElement.getElementsByTagName("Vertex");
			List<Position> vertexes =  new ArrayList<Position>();
			
			for(int j = 0; j<vertexesNodes.getLength();++j){
				Element vertexElement = (Element)vertexesNodes.item(j);
				float x = Float.parseFloat(vertexElement.getAttribute("x"));
				float y = Float.parseFloat(vertexElement.getAttribute("y"));
				
				vertexes.add(new Position(x,y));
			}				
			constraints.add(new Constraint(vertexes));
		}
		
		
		atcc.setConstraints(constraints);
	}

	private void loadNodesIfAvailable(Element managerElement,IMobilityManager mm) {
		if(mm instanceof IAbleToNodeConfiguration){
			IAbleToNodeConfiguration atnc = (IAbleToNodeConfiguration)mm;
			loadNodes(managerElement, atnc);
		}
	}

	private void loadNodes(Element managerElement, IAbleToNodeConfiguration atnc) {
		List<Node> nodes = new ArrayList<Node>();
		
		NodeList nodesNodes = managerElement.getElementsByTagName("Node");
		for(int j=0;j<nodesNodes.getLength();++j){
			Element nodeElement = (Element)nodesNodes.item(j);
			Node n = new Node();
			nodes.add(n);
				
			float x = Float.parseFloat(nodeElement.getAttribute("x"));
			float y = Float.parseFloat(nodeElement.getAttribute("y"));
			float r = Float.parseFloat(nodeElement.getAttribute("r"));
			n.setRadius(r);
			n.setPosition(new Position(x, y));
		}
		
		atnc.setNodes(nodes);
	}
	private void loadTreasures(Element managerElement, IAbleToTreasureConfiguration atnc) {
		List<Treasure> treasure = new ArrayList<Treasure>();
		
		NodeList treasuresNodes = managerElement.getElementsByTagName("Treasure");
		for(int j=0;j<treasuresNodes.getLength();++j){
			Element nodeElement = (Element)treasuresNodes.item(j);
			Treasure n = new Treasure();
			treasure.add(n);
				
			float x = Float.parseFloat(nodeElement.getAttribute("x"));
			float y = Float.parseFloat(nodeElement.getAttribute("y"));
			float r = Float.parseFloat(nodeElement.getAttribute("r"));
			n.setRadius(r);
			n.setPosition(new Position(x, y));
		}
		
		atnc.setTreasures(treasure);
	}
}
